package com.leo.boot.util;

import cn.hutool.core.collection.CollUtil;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collector;

import static cn.hutool.core.bean.BeanUtil.getProperty;
import static cn.hutool.core.collection.CollUtil.getFirst;
import static cn.hutool.core.collection.CollUtil.isEmpty;
import static cn.hutool.core.collection.IterUtil.size;
import static cn.hutool.core.text.CharSequenceUtil.EMPTY;
import static cn.hutool.core.text.StrPool.DASHED;
import static cn.hutool.core.text.StrPool.DOT;
import static java.util.Arrays.stream;
import static java.util.UUID.randomUUID;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static org.apache.commons.lang3.ObjectUtils.anyNull;

@Slf4j
@UtilityClass
public class CommonUtils {

    public static <T> Function<T, String> compose(String... fields) {
        return instance -> stream(fields).map(f -> getProperty(instance, f)).map(String::valueOf).collect(joining(DOT));
    }

    public static <T> Collector<T, ?, List<T>> duplicate(String... fields) {
        return collectingAndThen(
                groupingBy(compose(fields)),
                a -> a.values().stream().filter(l -> l.size() > 1).flatMap(List::stream).collect(toList())
        );
    }

    public static <T> Collector<T, ?, List<T>> distinct(String... fields) {
        return collectingAndThen(
                groupingBy(compose(fields)),
                a -> a.values().stream().map(CollUtil::getFirst).collect(toList())
        );
    }

    public static <T> Collector<T, ?, List<T>> reduce(BinaryOperator<T> op, String... fields) {
        return collectingAndThen(
                toMap(compose(fields), identity(), op),
                a -> new ArrayList<>(a.values())
        );
    }

    public static <T> BinaryOperator<T> logging() {
        return (a, b) -> {
            log.warn("{} and {} were duplicate", a, b);
            return a;
        };
    }

    public static <T> BinaryOperator<T> ignore() {
        return (a, b) -> a;
    }

    public static String uuid() {
        return randomUUID().toString().replace(DASHED, EMPTY);
    }

    public static <T, U, K> void fill(List<? extends T> instances, List<? extends U> values, BiConsumer<T, U> mapper, Function<T, K> insKey, Function<U, K> valKey) {
        if (anyNull(mapper, insKey, valKey)) {
            return;
        }
        if (isEmpty(instances) || isEmpty(values)) {
            log.debug("instances size {} and values size {}", size(instances), size(values));
            return;
        }
        String insName = getFirst(instances).getClass().getSimpleName();
        String valName = getFirst(values).getClass().getSimpleName();
        log.debug("fill {} {} with {} {}", size(instances), insName, size(values), valName);

        Map<K, ? extends U> valMap = values.stream().collect(toMap(valKey, identity(), logging()));
        instances.forEach(instance -> {
            K key = insKey.apply(instance);
            if (key == null) {
                return;
            }
            U value = valMap.get(key);
            if (value == null) {
                return;
            }
            mapper.accept(instance, value);
        });
    }
}
